import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

# 随机创建具有不同量纲的数据集
np.random.seed(42)
# 特征1：0-100的随机数，特征2：0-1的随机数
X = np.hstack([np.random.uniform(0, 100, (20, 1)),
               np.random.uniform(0, 1, (20, 1))])
# 定义分类阈值
threshold = 0.5
# 分类阈值，当特征2的值大于0.5时，标签为类别1
y = (X[:, 1] > threshold).astype(int)

# 手动划分训练集和测试集
X_train, y_train = X[:15], y[:15]  # 前15个训练
X_test, y_test = X[15:], y[15:]     # 后5个测试

def knn_test(X_tr, X_te, y_tr, y_te):
    """KNN分类测试函数"""
    model = KNeighborsClassifier(n_neighbors=3)
    model.fit(X_tr, y_tr)
    pred = model.predict(X_te)
    return accuracy_score(y_te, pred)

# 未归一化的准确率
raw_acc = knn_test(X_train, X_test, y_train, y_test)

# 标准归一化处理
# 训练集的预处理需应用于测试集和新数据，使用训练集的统计量（均值、方差），确保数据同分布
scaler = StandardScaler().fit(X_train)  # 拟合训练集，获取Scaler().fit(X_train)
X_train_scaled = scaler.transform(X_train)  # 转换训练集
X_test_scaled = scaler.transform(X_test)   # 转换测试集（使用训练集的统计量）

# 归一化后的准确率
scaled_acc = knn_test(X_train_scaled, X_test_scaled, y_train, y_test)

# 生成预测结果
raw_pred = KNeighborsClassifier(n_neighbors=3).fit(X_train, y_train).predict(X_test)
scaled_pred = KNeighborsClassifier(n_neighbors=3).fit(X_train_scaled, y_train).predict(X_test_scaled)

# 打印结果对比
print(f"原始数据分布：特征1范围 ({X[:,0].min():.1f}, {X[:,0].max():.1f})")
print(f"特征2范围 ({X[:,1].min():.2f}, {X[:,1].max():.2f})")
print("测试集真实标签：", y_test)
print("未归一化预测值：", raw_pred)
print("归一化后预测值：", scaled_pred)
print("准确率对比：")
print(f"未归一化准确率: {raw_acc:.2f}")
print(f"归一化后准确率: {scaled_acc:.2f}")

# 实验结果可视化
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

# 设置全局字体
plt.rcParams.update({
    'font.size': 14,          # 全局字体大小
    'axes.titlesize': 16,     # 标题字体大小
    'axes.labelsize': 12,     # 坐标轴标签字体大小
    'xtick.labelsize': 12,    # x轴刻度字体
    'ytick.labelsize': 12,    # y轴刻度字体
    'legend.fontsize': 12     # 图例字体
})
plt.rcParams['font.sans-serif'] = ['KaiTi']  # 中文字体
plt.rcParams['mathtext.fontset'] = 'stix'     # 数学字体
plt.rcParams['axes.unicode_minus'] = False              # 强制使用ASCII减号
plt.rcParams['figure.constrained_layout.use'] = True   # 启用自动布局

# 创建带自动布局的Figure
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6),
                             layout='constrained')  # 使用新版布局引擎

# 循环遍历训练集和测试集（idx=0:训练集, idx=1:测试集）
for idx, (data, target, marker) in enumerate(zip(
        [X_train, X_test], [y_train, raw_pred], ['o', '^'])):  # 测试集使用预测标签

    # 类别0数据点（使用预测标签）
    ax1.scatter(data[:, 0][target == 0], data[:, 1][target == 0],
                c='green', s=80, marker=marker,
                edgecolor='k', alpha=0.8)

    # 类别1数据点（使用预测标签）
    ax1.scatter(data[:, 0][target == 1], data[:, 1][target == 1],
                c='crimson', s=80, marker=marker,
                edgecolor='k', alpha=0.8)





ax1.axhline(threshold, color='limegreen', linestyle='--', lw=2)
ax1.set(xlabel='特征1 (范围: 0-100)', ylabel='特征2 (范围: 0-1)',
        title='原始数据分布(未归一化)',
        xlim=(0, 100), ylim=(-2, 2)) #横轴纵轴范围固定


# 标准化后数据散点图（坐标轴优化）
scaled_data = [X_train_scaled, X_test_scaled]
for idx, (data, target, marker) in enumerate(zip(
    scaled_data, [y_train, scaled_pred], ['o', '^'])): # 测试集使用预测标签
    ax2.scatter(data[:,0][target==0], data[:,1][target==0],
               c='green', s=80, marker=marker,
               edgecolor='k', alpha=0.8)
    ax2.scatter(data[:,0][target==1], data[:,1][target==1],
               c='crimson', s=80, marker=marker,
               edgecolor='k', alpha=0.8)
# 决策边界
scaled_boundary = (threshold - scaler.mean_[1]) / scaler.scale_[1]
ax2.axhline(scaled_boundary, color='limegreen', linestyle='--', lw=2)
ax2.set(xlabel='特征1 (Z-Score)', ylabel='特征2 (Z-Score)',
       title='标准归一化化后数据分布', xlim=(-2, 2), ylim=(-2, 2))  # 限制标准化后坐标范围

# 创建统一图例（优化定位）
legend_elements = [
    mpatches.Patch(facecolor='green', label='类别0'),
    mpatches.Patch(facecolor='crimson', label='类别1'),
    plt.Line2D([], [], marker='o', color='k', label='训练集',markerfacecolor='none',
              markersize=12, linestyle=''),
    plt.Line2D([], [], marker='^', color='k', label='测试集', markerfacecolor='none',
              markersize=12, linestyle=''),
    plt.Line2D([], [], color='limegreen', linestyle='--', label='决策边界')
]

fig.legend(handles=legend_elements, loc='outside upper center',
          ncol=5, columnspacing=2, frameon=False)

# 移除冗余的布局调整调用
plt.show()
